﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using HydroDesktop.Interfaces.ObjectModel;
using HydroDesktop.Database;
using HydroDesktop.Interfaces;

namespace DroughtAnalysis
{
    /// <summary>
    /// Helper class for writing drought time series
    /// </summary>
    public class DroughtTimeSeriesWriter
    {
        /// <summary>
        /// Initializes the drought time series writer
        /// with default values
        /// </summary>
        public DroughtTimeSeriesWriter()
        {
            IndexFileName = "sucha-rada.txt";
            IndexColumnName = "S Index";
            DateColumnName = "Od";
            Delimiter = '\t';
            DroughtMethodName = "Mass Curve Method";
            DroughtVariableName = "S-Index";
        }

        /// <summary>
        /// Name of the drought index calculation method
        /// </summary>
        public string DroughtMethodName { get; set; }

        /// <summary>
        /// Name of the drought index variable (S-Index)
        /// </summary>
        public string DroughtVariableName { get; set; }

        public string DroughtMethodUnitName { get; set; }

        /// <summary>
        /// Name of the drought index file
        /// </summary>
        public string IndexFileName { get; set; }

        /// <summary>
        /// Name of the date/time column in the drought index file
        /// </summary>
        public string DateColumnName { get; set; }

        /// <summary>
        /// Name of the drought index column in the drought index file
        /// </summary>
        public string IndexColumnName { get; set; }

        /// <summary>
        /// The delimiter in the drought index file
        /// </summary>
        public char Delimiter { get; set; }

        /// <summary>
        /// The output time step (year, month, or day)
        /// </summary>
        public string OutputTimeStep { get; set; }
        
        /// <summary>
        /// Writes the drought index from a file to a database
        /// </summary>
        /// <param name="siteInfo"></param>
        public void WriteDroughtIndex(DroughtSiteInfo siteInfo)
        {
            //locate the file
            string dir = siteInfo.ResultDirectory;
            if (!Directory.Exists(dir) )
                throw new ArgumentException("The directory " + dir + " doesn't exist.");

            string filePath = Path.Combine(dir, IndexFileName);
            if (!File.Exists(filePath))
                throw new ArgumentException("The drought index file " + filePath + " doesn't exist.");

            //read drought index array
            DroughtIndexSeriesInfo rawSeriesData = null;
            if (OutputTimeStep == "year")
            {
                rawSeriesData = ReadDroughtIndexFromYearFile(filePath, IndexColumnName, Delimiter);
                rawSeriesData.TimeStep = "year";
            }
            else if (OutputTimeStep == "month")
            {
                rawSeriesData = ReadDroughtIndexFromMonthFile(filePath, IndexColumnName, Delimiter);
                rawSeriesData.TimeStep = "month";
            }
            else
            {
                rawSeriesData = ReadDroughtIndexFromFile(filePath, DateColumnName, IndexColumnName, Delimiter);
            }

            //prepare series for writing to database
            Series droughtSeries = CreateHydroDesktopDroughtSeries(siteInfo.Site, rawSeriesData);

            //save to HydroDesktop db
            Theme droughtTheme = new Theme("Drought");
            var dbm = RepositoryFactory.Instance.Get < IRepositoryManager>(DatabaseTypes.SQLite, HydroDesktop.Configuration.Settings.Instance.DataRepositoryConnectionString);
            dbm.SaveSeries(droughtSeries, droughtTheme, OverwriteOptions.Append);
        }

        private Variable CreateDroughtVariable()
        {
            Variable v = new Variable();
            v.Code = DroughtVariableName + ":" + 1;
            v.DataType = "Average";
            v.GeneralCategory = "Climate";
            v.IsCategorical = false;
            v.IsRegular = true;
            v.Name = DroughtVariableName;
            v.NoDataValue = -9999.0;
            v.SampleMedium = "Air";
            v.Speciation = "Not Applicable";
            v.TimeSupport = 1.0;
            v.TimeUnit = new Unit { Abbreviation = "d", Name = "Day", UnitsType = "time" };
            v.ValueType = "Model Result";
            //v.VariableUnit = new Unit { Abbreviation = "[]", Name = " ", UnitsType = "relative" };
            v.VariableUnit = new Unit { Abbreviation = DroughtMethodUnitName, Name = DroughtMethodUnitName, UnitsType = "Unknown" };
            v.VocabularyPrefix = DroughtVariableName;
            
            return v;
        }

        private Method CreateDroughtMethod(string methodName)
        {
            Method m = Method.Unknown;
            m.Description = methodName;
            return m;
        }

        private QualityControlLevel CreateDroughtQC()
        {
            QualityControlLevel qc = QualityControlLevel.Unknown;
            qc.Definition = "Quality Controlled Data";
            return qc;
        }

        private Source CreateDroughtSource()
        {
            Source src = Source.Unknown;
            src.Organization = "DroughtAnalyzer";
            return src;
        }

        private Series CreateHydroDesktopDroughtSeries(Site station, DroughtIndexSeriesInfo values)
        {
            Variable v = CreateDroughtVariable();
            Method m = CreateDroughtMethod(DroughtMethodName);
            Source src = CreateDroughtSource();
            QualityControlLevel qc = CreateDroughtQC();
            Series s = new Series(station, v, m, qc, src);
            DateTime curDate = values.StartDate;
            List<double> vals = values.DroughtIndexes;

            if (values.TimeStep == "year")
            {
                foreach (double val in vals)
                {
                    s.AddDataValue(curDate, val);
                    curDate = curDate.AddYears(1);
                }
            }
            else if (values.TimeStep == "month")
            {
                foreach (double val in vals)
                {
                    s.AddDataValue(curDate, val);
                    curDate = curDate.AddMonths(1);
                }
            }
            else
            {
                foreach (double val in vals)
                {
                    s.AddDataValue(curDate, val);
                    curDate = curDate.AddDays(1);
                }
            }
            return s;
        }

        //todo re-factor this
        private DroughtIndexSeriesInfo ReadDroughtIndexFromYearFile(string fileName, string valueColumn, char delimiter)
        {
            if (!File.Exists(fileName))
                throw new ArgumentException("The drought index file " + fileName + " doesn't exist.");

            string dateColumn = "Rok";
            
            //read the file
            string line = String.Empty;
            char[] separator = new char[] { delimiter };
            using (StreamReader reader = new StreamReader(fileName))
            {
                // find the date column and value column headers 
                string firstLine = reader.ReadLine();
                string[] headers = firstLine.Split(separator);

                int dateColumnIndex = -1;
                int valueColumnIndex = -1;
                for (int i = 0; i < headers.Length; i++)
                {
                    if (headers[i].ToLower().Trim() == dateColumn.ToLower().Trim())
                        dateColumnIndex = i;
                    else if (headers[i].ToLower().Trim() == valueColumn.ToLower().Trim())
                        valueColumnIndex = i;
                }

                if (dateColumnIndex == -1)
                    throw new ArgumentException("header column with name '" + dateColumn + "' not found in the drought index file.");

                if (valueColumnIndex == -1)
                    throw new ArgumentException("index column with name '" + valueColumn + "' not found in the drought index file.");

                List<double> valueList = new List<Double>();

                //read the initial date
                string secondLine = reader.ReadLine();

                if (secondLine == null)
                    throw new InvalidDataException("The drought index file must have at least two lines.");

                string[] vals = secondLine.Split(separator);

                DateTime startDate;
                try
                {
                    startDate = new DateTime(Convert.ToInt32(vals[dateColumnIndex]), 1, 1);
                }
                catch (FormatException fe)
                {
                    throw new FormatException("The date in the drought index file is in invalid format: " + vals[dateColumnIndex]);
                }

                double startValue = Convert.ToDouble(vals[valueColumnIndex], CultureInfo.InvariantCulture);
                valueList.Add(startValue);

                while ((line = reader.ReadLine()) != null)
                {
                    vals = line.Split(separator);
                    valueList.Add(Convert.ToDouble(vals[valueColumnIndex], CultureInfo.InvariantCulture));
                }

                return new DroughtIndexSeriesInfo { StartDate = startDate, DroughtIndexes = valueList };
            }
        }

        private DroughtIndexSeriesInfo ReadDroughtIndexFromMonthFile(string fileName, string valueColumn, char delimiter)
        {
            if (!File.Exists(fileName))
                throw new ArgumentException("The drought index file " + fileName + " doesn't exist.");

            string yearColumn = "Rok";
            string monthColumn = "Mesic";

            //read the file
            string line = String.Empty;
            char[] separator = new char[] { delimiter };
            using (StreamReader reader = new StreamReader(fileName))
            {
                // find the date column and value column headers 
                string firstLine = reader.ReadLine();
                string[] headers = firstLine.Split(separator);

                int yearColumnIndex = -1;
                int monthColumnIndex = -1;
                int valueColumnIndex = -1;
                for (int i = 0; i < headers.Length; i++)
                {
                    if (headers[i].ToLower().Trim() == yearColumn.ToLower().Trim())
                        yearColumnIndex = i;
                    else if (headers[i].ToLower().Trim() == monthColumn.ToLower().Trim())
                        monthColumnIndex = i;
                    else if (headers[i].ToLower().Trim() == valueColumn.ToLower().Trim())
                        valueColumnIndex = i;
                }

                if (yearColumnIndex == -1)
                    throw new ArgumentException("header column with name '" + yearColumn + "' not found in the drought index file.");

                if (monthColumnIndex == -1)
                    throw new ArgumentException("header column with name '" + monthColumn + "' not found in the drought index file.");

                if (valueColumnIndex == -1)
                    throw new ArgumentException("index column with name '" + valueColumn + "' not found in the drought index file.");

                List<double> valueList = new List<Double>();

                //read the initial date
                string secondLine = reader.ReadLine();

                if (secondLine == null)
                    throw new InvalidDataException("The drought index file must have at least two lines.");

                string[] vals = secondLine.Split(separator);

                DateTime startDate;
                try
                {
                    startDate = new DateTime(Convert.ToInt32(vals[yearColumnIndex]), Convert.ToInt32(vals[monthColumnIndex]), 1);
                }
                catch (FormatException fe)
                {
                    throw new FormatException("The date in the drought index file is in invalid format: " + vals[monthColumnIndex] + " " + vals[yearColumnIndex]);
                }

                double startValue = Convert.ToDouble(vals[valueColumnIndex], CultureInfo.InvariantCulture);
                valueList.Add(startValue);

                while ((line = reader.ReadLine()) != null)
                {
                    vals = line.Split(separator);
                    valueList.Add(Convert.ToDouble(vals[valueColumnIndex], CultureInfo.InvariantCulture));
                }

                return new DroughtIndexSeriesInfo { StartDate = startDate, DroughtIndexes = valueList };
            }
        }

        private DroughtIndexSeriesInfo ReadDroughtIndexFromFile(string fileName, string dateColumn, string valueColumn, char delimiter)
        {
            if (!File.Exists(fileName))
                throw new ArgumentException("The drought index file " + fileName + " doesn't exist.");

            //read the file
            string line = String.Empty;
            char[] separator = new char[] { delimiter };
            using (StreamReader reader = new StreamReader(fileName))
            {
                // find the date column and value column headers 
                string firstLine = reader.ReadLine();
                string[] headers = firstLine.Split(separator);

                int dateColumnIndex = -1;
                int valueColumnIndex = -1;
                for(int i=0; i< headers.Length; i++)
                {
                    if (headers[i].ToLower().Trim() == dateColumn.ToLower().Trim())
                        dateColumnIndex = i;
                    else if (headers[i].ToLower().Trim() == valueColumn.ToLower().Trim())
                        valueColumnIndex = i;
                }

                if (dateColumnIndex == -1)
                    throw new ArgumentException("header column with name '" + dateColumn + "' not found in the drought index file.");

                if (valueColumnIndex == -1)
                    throw new ArgumentException("index column with name '" + valueColumn + "' not found in the drought index file.");

                List<double> valueList = new List<Double>();

                //read the initial date
                string secondLine = reader.ReadLine();

                if (secondLine == null)
                    throw new InvalidDataException("The drought index file must have at least two lines.");

                string[] vals = secondLine.Split(separator);

                DateTime startDate;
                try
                {
                    startDate = Convert.ToDateTime(vals[dateColumnIndex]);
                }
                catch(FormatException fe)
                {
                    throw new FormatException("The date in the drought index file is in invalid format: " + vals[dateColumnIndex]);
                }

                string startValueString = vals[valueColumnIndex];
                double startValue = startValueString == "NA" ? -9999.0 : Convert.ToDouble(vals[valueColumnIndex], CultureInfo.InvariantCulture);
                valueList.Add(startValue);
                
                while ((line = reader.ReadLine()) != null)
                {
                    vals = line.Split(separator);
                    string val = vals[valueColumnIndex];
                    if (val == "NA")
                        valueList.Add(-9999.0);
                    else
                        valueList.Add(Convert.ToDouble(vals[valueColumnIndex], CultureInfo.InvariantCulture));
                }

                return new DroughtIndexSeriesInfo { StartDate = startDate, DroughtIndexes = valueList };
            }
        }

        private class DroughtIndexSeriesInfo
        {
            /// <summary>
            /// Start date of the drought index
            /// </summary>
            public DateTime StartDate { get; set; }

            public string TimeStep { get; set; }

            /// <summary>
            /// series of drought indexes (1 day time step)
            /// </summary>
            public List<Double> DroughtIndexes { get; set; }
        }
    }
}
